#ifndef __MY_SERVER__
#define __MY_SERVER__ 
#include "data.hpp"
#include "httplib.h"
#include <sstream>

extern cloud::DataManager *_data;
namespace cloud 
{
  class Server 
  {
    private:
      httplib::Server _srv;
      int _srv_port = 9090;//服务器绑定监听端口号为9090
      std::string _url_prefix = "/download/";//前缀信息
      std::string _backup_dir = "./backup_dir/";//上传文件的备份存储路径
    private:
      //上传接口
      static void Upload(const httplib::Request &req, httplib::Response &rsp)
      {
        std::string _backup_dir = "./backup_dir/";//上传文件的备份存储路径

        //判断有没有对应标识的文件上传区域数据
        if (req.has_file("file")== false)        
          //与客户端中的item.name = "file";相对应    
        {    
          std::cout << "upload file data format error\n";
          rsp.status = 400;
          return ;
        }
        //获取解析后的区域数据
        httplib::MultipartFormData data = req.get_file_value("file");
        //此处realpath为实际存储路径名，备份存储路径+文件名
        std::string realpath = _backup_dir + data.filename;
        // 将data.content写入到实际存储路径名中，实际上就是把文件备份起来
        if(FileUtil(realpath).Write(data.content) == false)
        {
          std::cout << "back file failed\n";//备份失败
          rsp.status = 500;//500是服务器内部错误
          return ;
        }
        //新增备份信息
        if(_data->Insert(realpath) == false)
        {
          //如果想数据管理模块新增数据失败也算是服务器内部错误
          std::cout << "insert back info failed!\n";
          rsp.status = 500;
          return ;
        }
        rsp.status = 200;//代表上传成功
        std::cout << "new backup: " << realpath << std::endl;//打印上传成功的信息 
        return ;
      }
      static std::string StrTime(time_t t)
      {
        
        return std::asctime(std::localtime(&t));
      }
      //查看列表接口页面
      static void List(const httplib::Request &req, httplib::Response &rsp)
      {
        //获取所有历史备份信息，并根据信息组织出一个html页面，响应正文
        std::vector<FileInfo> arry;
        if(_data->SelectAll(&arry) == false)
        {
          std::cout << "select all back info failed!\n";//获取所有备份信息失败
          rsp.status = 500;
          return ;
        }
        std::stringstream ss;//字符串流
        ss << "<html>";
        ss <<   "<heal>";
        ss <<     "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>";
        ss <<     "<title>Download</title>";
        ss <<   "</head>";
        ss <<   "<body>";
        ss <<     "<h1>Download</h1>";
        ss <<     "<table>";
        for (auto &a : arry)
        {
          //组织每一行的页面标签
          ss <<     "<tr>";
                      //<td><a href="/download/test.txt">test.txt</a></td>;文件链接
          ss <<       "<td><a href='"<<a.url_path<<"'>" <<a.filename << "</a></td>";
                      //<td align="right"> 2022-02-25 10:10:10 </td>时间信息
          ss <<       "<td align='right'>" << StrTime(a.back_time) << "</td>";
                      //文件大小信息字节为单位
          ss <<       "<td align='right'>" << a.file_size / 1024 << "KB </td>";
          ss <<     "</tr>";
        }
        ss <<     "</table>";
        ss <<   "</body>";
        ss << "</html>";
        //设置正文第一个参数为正文，第二个参数为正文类型html超文本数据
        rsp.set_content(ss.str(), "text/html");
        rsp.status = 200;
        return ;
      }
      static std::string StrETag(const std::string &filename)
      {
        //etag是一个文件的唯一标识，当文件被修改则会发生改变
        //这里etag不用内容计算:文件大小-文件最后修改时间
        time_t mtime = FileUtil(filename).MTime();//文件最后一次修改时间
        size_t fsize = FileUtil(filename).Size();//文件大小
        std::stringstream ss;//字符串流
        ss << fsize << "-" << mtime;
        return ss.str();
      }
      //下载接口
      static void Download(const httplib::Request &req, httplib::Response &rsp)
      {
        FileInfo info;
        if(_data->SelectOne(req.path, &info) == false)//根据资源路径获取单个信息
        {
          std::cout << "select one back info failed!\n";//获取单个备份信息失败
          rsp.status = 404;//请求的资源未找到
          return ;
        }
        //如果文件已经被压缩了，则要先解压缩，然后再读取
        if (info.pack_flag == true)
        {
          FileUtil(info.pack_path).UnCompress(info.real_path);
        }
        if (req.has_header("If-Range"))//判断头信息有没有对应的If-Range字段，是否有断点续传请求
        {
          //If-Range:ETag信息
          std::string old_etag = req.get_header_value("If-Range");
          //Range：bytes=200-1000
          //using Range = std::pair<ssize_t,ssize_t>;Range里面是pair
          //using Ranges = std::vector<Range>; 数组里面是Range
          //size_t start = req.Ranges[0].first;
          std::string cur_etag = StrETag(info.real_path);
          if(old_etag == cur_etag)//文件没有改变可以断点续传
          {
            //Range：bytes=200-1000如果我们自己处理进行字符串解析得到起始和结束位置就行
            //size_t start = req.Ranges[0].first;//但是httplib已经替我们解析了
            //如果没有末尾，end则表示到达文件末尾，httplib会将second设置为-1；
            //size_t end = req.Ranges[0].second;
            //这时候从文件start开始读取end-start+1长度的数据,如果end为-1,则是文件长度-start长度的数据
            //因为假设1000长度的文件，请求900-999，则返回包含900和999在内的总共100长度数据
            //而如果请求900-，相当于到达文件末尾1000长度末尾位置就是999，直接长度-900就可以了
            //
            //httplib已经替我们完成了断点续传的功能我们只需要将文件的所有数据放到body中
            //然后设置响应状态码为206，httplib检测到响应状态码是206，就会从body中截取指定区间数据响应
            std::cout << req.ranges[0].first << "-" << req.ranges[0].second << std::endl;
            FileUtil(info.real_path).Read(&rsp.body);//根据实际存储信息读取文件数据到响应正文之中
            rsp.set_header("Content-Type", "application/octet-stream");
            rsp.set_header("Accept-Ranges","bytes");//告诉客户端，支持断点续传功能
            //rsp.set_header("Content-Range", "bytes start-end/fsize");//httplib会自动设置 
            rsp.set_header("ETag", cur_etag);
            rsp.status = 206;
            return ;
          }
        }
        //根据实际存储信息读取文件数据到响应正文之中
        FileUtil(info.real_path).Read(&rsp.body);
        //设置正文类型为二进制流
        rsp.set_header("Content-Type", "application/octet-stream");
        rsp.set_header("Accept-Ranges","bytes");//告诉客户端，支持断点续传功能
        rsp.set_header("ETag", StrETag(info.real_path));
        rsp.status = 200;
        return ;
      }
    public:
      Server()
      {
        FileUtil(_backup_dir).CreateDirectory();//创建对应目录
      }
      bool RunModule()//模块运行接口
      {
        //搭建http服务器
        //建立请求处理函数映射关系
        //Post(请求的资源路径，对应的业务处理回调函数),对应上传接口
        _srv.Post("/upload",Upload);
        //对应查看页面接口
        _srv.Get("/list",List);
        //对应下载接口
        //下载资源路径的正则表达式为_url_prefix+"(\\.\\*)"
        std::string regex_download_path = _url_prefix+"(.*)";
        std::cout << regex_download_path << std::endl;
        _srv.Get(regex_download_path, Download);//匹配任意一个字符一次或多次
        //启动服务器
        _srv.listen("0.0.0.0", _srv_port);
        return true;
      }
  };
}

#endif 
